package dkr.core.serialize;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import dkr.core.cache.FixedCacheMap;


/**
 * @author xuyd
 *
 */
public class SerializeObjectHandle {
	private String name;
	private Class<?> mapClass;
	private Map<String, Field> entryFields;
	private Map<String, Method> entryGetters;
	private Map<String, Method> entrySetters;
	private Map<String, SerializeTypeHandle> entryHandlers;
	
	private static FixedCacheMap<String, SerializeObjectHandle> handlers;
	static{
		handlers = new FixedCacheMap<String, SerializeObjectHandle>(300);
	}
	
	public static SerializeObjectHandle getHandler(Class<?> clazz){
		if(clazz == null || !PHPSerializer.isPHPSerializable(clazz))
			return null;
		String key = clazz.getName();
		SerializeObjectHandle handler = handlers.get(key);
		if(handler == null){
			handler = new SerializeObjectHandle(clazz);
			handler.initMapClass(clazz);
			if(handler.entryFields != null && !handler.entryFields.isEmpty()){
				handlers.put(key, handler);
			}
			else{
				handler = null;
			}
		}
		return handler;
	}
	
	private SerializeObjectHandle(Class<?> clazz){
		entryFields = new HashMap<String, Field>();
		entrySetters = new HashMap<String, Method>();
		entryGetters = new HashMap<String, Method>();
		entryHandlers = new HashMap<String, SerializeTypeHandle>();
		name = clazz.getName();
		mapClass = clazz; 
	}
	
	private void initMapClass(Class<?> clazz){
		Field[] fds = clazz.getDeclaredFields();
		String className = clazz.getName();
		if(fds != null){
			for(Field fd : fds){
				String fieldName = fd.getName();
				if(entryFields.containsKey(fieldName))
					continue;
				if(!fd.isAccessible())
					fd.setAccessible(true);
				Type fieldType = fd.getGenericType();
				SerializeTypeHandle handler = SerializeTypeHandle.getHandler(fieldType);
				if(handler != null){
					entryFields.put(fieldName, fd);
					entryHandlers.put(fieldName, handler);
				}
				else{
					System.err.printf("MapStruct [%s]'s entry field [%s] has an unsupported type [%s], the field will be skipped.\r\n", className, fieldName, fieldType.toString());
				}
			}
		}
		Method[] mds = clazz.getDeclaredMethods();
		if(mds != null && mds.length > 0){
			for(Method md : mds){
				int mod = md.getModifiers();
				if(Modifier.isPublic(mod) && !Modifier.isStatic(mod)){
					String methodName = md.getName();
					if(methodName.startsWith("get")){
						if(md.getReturnType() == Void.TYPE || md.getParameterTypes().length > 0)
							continue;
						String fieldName = String.valueOf(methodName.charAt(3)).toLowerCase() + methodName.substring(4);
						if(entryGetters.containsKey(fieldName))
							continue;
						if(entryFields.containsKey(fieldName)){
							entryGetters.put(fieldName, md);
						}
					}
					else if(methodName.startsWith("is")){
						if(md.getParameterTypes().length > 0)
							continue;
						if(md.getReturnType() == Boolean.TYPE || md.getReturnType() == Boolean.class){
							String fieldName = String.valueOf(methodName.charAt(2)).toLowerCase() + methodName.substring(3);
							if(entryGetters.containsKey(fieldName))
								continue;
							if(entryFields.containsKey(fieldName)){
								entryGetters.put(fieldName, md);
							}
						}
					}
					else if(methodName.startsWith("set")){
						if(md.getReturnType() != Void.TYPE || md.getParameterTypes().length != 1)
							continue;
						String fieldName = String.valueOf(methodName.charAt(3)).toLowerCase() + methodName.substring(4);
						if(entrySetters.containsKey(fieldName))
							continue;
						if(entryFields.containsKey(fieldName)){
							entrySetters.put(fieldName, md);
						}
					}
				}
			}
		}
		clazz = clazz.getSuperclass();
		if(PHPSerializer.isPHPSerializable(clazz))
			initMapClass(clazz);
	}

	public String getName() {
		return name;
	}

	public Class<?> getMapClass() {
		return mapClass;
	}
	
	public static LinkedHashMap<String, Object> toMap(Object obj){
		if(obj == null)
			return new LinkedHashMap<String, Object>();
		Class<?> clazz = obj.getClass();
		SerializeObjectHandle handle = getHandler(clazz);
		if(handle == null)
			return new LinkedHashMap<String, Object>();
		
		return handle.serialize(obj);
	}
	
	public static Object toSerializable(Object obj){
		if(obj == null)
			return null;
		SerializeTypeHandle handle = SerializeTypeHandle.getSerializeType(obj.getClass());
		if(handle == null)
			return null;
		if(handle.getSerializeType() == SerializeType.OBJECT){
			return toMap(obj);
		}
		else{
			return handle.serialize(obj);
		}
	}
	
	private LinkedHashMap<String, Object> serialize(Object obj){
		LinkedHashMap<String, Object> results = new LinkedHashMap<String, Object>();
		Object[] args = new Object[0];
		for(String fieldName : entryFields.keySet()){
			Field fd = entryFields.get(fieldName);
			SerializeTypeHandle typeHandle = entryHandlers.get(fieldName);
			if(fd == null || typeHandle == null)
				continue;
			Object val = null;
			Method getter = entryGetters.get(fieldName);
			try {
				if(getter == null){
					val = fd.get(obj);
				}
				else{
					val = getter.invoke(obj, args);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			Object ret = typeHandle.serialize(val);
			if(ret != null)
				results.put(fieldName, ret);
		}
		return results;
	}
	
	public static Object toBean(Class<?> clazz, Map<String, Object> map){
		if(clazz == null)
			return null;
		SerializeObjectHandle handle = getHandler(clazz);
		if(handle == null)
			return null;
		Object obj = null;
		try {
			obj = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if(obj == null)
			return null;
		if(map == null || map.isEmpty())
			return obj;
		
		handle.unserialize(obj, map);
		return obj;
	}
	
	private void unserialize(Object obj, Map<String, Object> map){
		for(String fieldName : entryFields.keySet()){
			Field fd = entryFields.get(fieldName);
			SerializeTypeHandle typeHandle = entryHandlers.get(fieldName);
			if(fd == null || entryHandlers == null)
				continue;
			Object val = map.get(fieldName);
			Object ret = typeHandle.unserialize(val);
			Method setter = entrySetters.get(fieldName);
			try {
				if(setter == null){
					fd.set(obj, ret);
				}
				else{
					setter.invoke(obj, new Object[]{ret});
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
